/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.slots.statistic;

import java.util.Collection;

import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException;
import com.alibaba.csp.sentinel.spi.SpiOrder;
import com.alibaba.csp.sentinel.util.TimeUtil;
import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.BlockException;

/**
 * <p>
 * A processor slot that dedicates to real time statistics.
 * When entering this slot, we need to separately count the following
 * information:
 * <ul>
 * <li>{@link ClusterNode}: total statistics of a cluster node of the resource ID.</li>
 * <li>Origin node: statistics of a cluster node from different callers/origins.</li>
 * <li>{@link DefaultNode}: statistics for specific resource name in the specific context.</li>
 * <li>Finally, the sum statistics of all entrances.</li>
 * </ul>
 * </p>
 *
 * @author jialiang.linjl
 * @author Eric Zhao
 */
@SpiOrder(-7000)
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        try {
            // Do some checking. 调用下一个规则处理器进行校验
            fireEntry(context, resourceWrapper, node, count, prioritized, args);
            // Request passed, add thread count and pass count.
            node.increaseThreadNum(); //当前defaultNode节点和clusterNode节点 统计通过 线程数+1
            node.addPassRequest(count);//当前defaultNode节点和clusterNode节点 统计通过 请求数+1
            if (context.getCurEntry().getOriginNode() != null) {//当先上下文context的当前Entry中originNode统计节点不为空
                // Add count for origin node.   originNode统计节点 线程数+1
                context.getCurEntry().getOriginNode().increaseThreadNum();
                context.getCurEntry().getOriginNode().addPassRequest(count);//originNode统计节点 请求通过数+1
            }
            if (resourceWrapper.getEntryType() == EntryType.IN) {//判断资源进入类型为 进入类型。比如SentinelWebInterception拦截器调用时。
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum(); //统计整个系统的全局线程数+1, 用于系统规则校验
                Constants.ENTRY_NODE.addPassRequest(count);//统计整个系统的请求通过数+1  用于系统规则校验
            }
            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);// 触发请求通过后，回调handle来进行处理。比如说热点参数，就是在这里统计的。
            }
        } catch (PriorityWaitException ex) {//在FlowSlot规则处理器的，QPS的快速失败效果中，会抛出此异常
            node.increaseThreadNum();//当前defaultNode节点和clusterNode节点 统计 线程通过数+1
            if (context.getCurEntry().getOriginNode() != null) {//当先上下文context的当前Entry中originNode统计节点不为空
                // Add count for origin node. originNode统计节点 线程数+1
                context.getCurEntry().getOriginNode().increaseThreadNum();
            }
            if (resourceWrapper.getEntryType() == EntryType.IN) {//判断资源进入类型为 进入类型。比如SentinelWebInterception拦截器调用时。
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();//统计整个系统的全局线程数+1, 用于系统规则校验
            }
            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);// 触发请求通过后，回调handle来进行处理。比如说热点参数，就是在这里统计的。
            }
        } catch (BlockException e) {//抛出规则校验不通过异常
            // Blocked, set block exception to current entry.
            context.getCurEntry().setBlockError(e);//往当前上下文的当前Entry设置规则校验不通过异常
            // Add block count. 统计 请求不通过数+1
            node.increaseBlockQps(count);
            if (context.getCurEntry().getOriginNode() != null) {//当先上下文context的当前Entry中originNode统计节点不为空
                context.getCurEntry().getOriginNode().increaseBlockQps(count);//originNode统计节点 请求不通过数+1
            }
            if (resourceWrapper.getEntryType() == EntryType.IN) {//判断资源进入类型为 进入类型。比如SentinelWebInterception拦截器调用时。
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseBlockQps(count);//统计整个系统的请求不通过数+1
            }
            // Handle block event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                //触发请求不通过的回调handle来进行处理。比如说热点参数，就是在这里统计的。
                handler.onBlocked(e, context, resourceWrapper, node, count, args);
            }
            throw e;
        } catch (Throwable e) {//抛出其他类型异常，这里的异常是未知错误，正常情况下不存在
            // Unexpected internal error, set error to current entry.
            context.getCurEntry().setError(e);
            throw e;
        }
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        Node node = context.getCurNode();//获取当前上下对应的当前统计节点
        if (context.getCurEntry().getBlockError() == null) {//判断是否存在校验不通过异常
            // Calculate response time (use completeStatTime as the time of completion).
            long completeStatTime = TimeUtil.currentTimeMillis();//获取当前时间
            context.getCurEntry().setCompleteTimestamp(completeStatTime);//往上下文中的Entry对象添加完成时间
            //通过完成时间 减去 开始时间，计算出响应时间rt
            long rt = completeStatTime - context.getCurEntry().getCreateTimestamp();
            //获取业务逻辑抛出的异常错误
            Throwable error = context.getCurEntry().getError();
            // Record response time and success count.
            //往当前统计节点node,中记录响应时间和成功次数
            recordCompleteFor(node, count, rt, error);
            //往当前originlNode,中记录响应时间和成功次数
            recordCompleteFor(context.getCurEntry().getOriginNode(), count, rt, error);
            //判断资源进入类型为 进入类型。比如SentinelWebInterception拦截器调用时。
            if (resourceWrapper.getEntryType() == EntryType.IN) {
                //往全局ClusterNode节点,中记录响应时间和成功次数
                recordCompleteFor(Constants.ENTRY_NODE, count, rt, error);
            }
        }
        // Handle exit event with registered exit callback handlers.
        //回调exit退出的处理器
        Collection<ProcessorSlotExitCallback> exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks();
        for (ProcessorSlotExitCallback handler : exitCallbacks) {
            handler.onExit(context, resourceWrapper, count, args);
        }
        //继续责任链中下一个规则处理器
        fireExit(context, resourceWrapper, count);
    }

    private void recordCompleteFor(Node node, int batchCount, long rt, Throwable error) {
        if (node == null) {//判断统计节点是否为空
            return;//直接返回
        }
        //统计rt时间，和成功次数
        node.addRtAndSuccess(rt, batchCount);
        //减去线程数
        node.decreaseThreadNum();
        //判断异常错误error不为空，且不是校验不通过异常
        if (error != null && !(error instanceof BlockException)) {
            //统计抛出异常次数+1
            node.increaseExceptionQps(batchCount);
        }
    }
}
